/*
 * $Id: session.c 450 2008-04-27 23:19:36Z jasta $
 *
 * Copyright (C) 2008 Josh Guilfoyle <jasta@devtcg.org>
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2, or (at your option) any
 * later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 */

#include "main.h"

#include <libsyncml/syncml.h>
#include <libsyncml/sml_ds_server.h>

#include "db.h"
#include "session.h"
#include "item.h"

/*****************************************************************************/

static const char *get_client_ident(FiveSession *fsess)
{
	SmlLocation *loc;
	const char *src;

	assert(fsess != NULL);

	loc = smlSessionGetSource(smlDsSessionGetSession(fsess->dsession));
	assert(loc != NULL);

	src = smlLocationGetURI(loc);
	assert(src != NULL);

	return src;
}

FiveSession *five_session_new(SmlDsSession *dsess)
{
	FiveSession *fsess;
	sqlite3_stmt *stmt;
	const char *sql;
	int rc;

	assert(dsess != NULL);

	fsess = g_malloc0(sizeof(FiveSession));
	fsess->dsession = dsess;

	if (db_open_again(&fsess->dbconn) == FALSE)
	{
		g_error("Failed to open database connection");
		g_free(fsess);
		return NULL;
	}

	sql =
	  "SELECT last, timestamp "
	  "  FROM sync_events "
	  " WHERE src = ?";

	rc = sqlite3_prepare(fsess->dbconn, sql, -1, &stmt, NULL);
	assert(rc == SQLITE_OK);

	sqlite3_bind_text(stmt, 1, get_client_ident(fsess), -1, NULL);

	if ((rc = sqlite3_step(stmt)) == SQLITE_ROW)
	{
		fsess->client.last = (time_t)sqlite3_column_int64(stmt, 0);
		fsess->server.last = (time_t)sqlite3_column_int64(stmt, 1);
	}

	sqlite3_finalize(stmt);

	return fsess;
}

void five_session_free(FiveSession *fsess)
{
	db_close_again(fsess->dbconn);
	g_free(fsess);
}

/*****************************************************************************/

gboolean five_session_item_map(FiveSession *fsess, const char *mime,
  sqlite_int64 guid, sqlite_int64 luid)
{
	sqlite3_stmt *stmt;
	const char *domain;
	const char *sql;
	int rc;

	/* TODO: Optimize. */
	if (strcmp(mime, "application/x-fivedb-artist") == 0)
		domain = "artist";
	else if (strcmp(mime, "application/x-fivedb-album") == 0)
		domain = "album";
	else if (strcmp(mime, "application/x-fivedb-song") == 0)
		domain = "song";

	sql =
	  "REPLACE INTO item_map (src, domain, guid, luid) VALUES (?, ?, ?, ?)";

	rc = sqlite3_prepare(fsess->dbconn, sql, -1, &stmt, NULL);
	assert(rc == SQLITE_OK);

	sqlite3_bind_text(stmt, 1, get_client_ident(fsess), -1, SQLITE_STATIC);
	sqlite3_bind_text(stmt, 2, domain, -1, SQLITE_STATIC);
	sqlite3_bind_int64(stmt, 3, guid);
	sqlite3_bind_int64(stmt, 4, luid);

	if ((rc = sqlite3_step(stmt)) != SQLITE_DONE)
	{
		g_critical("Failed to create item map");
		sqlite3_finalize(stmt);
		return FALSE;
	}

	sqlite3_finalize(stmt);
	return TRUE;
}

gboolean five_session_update_anchors(FiveSession *fsess)
{
	sqlite3_stmt *stmt;
	const char *sql;
	int rc;

	if (fsess->server.last == 0)
		sql = "INSERT INTO sync_events (last, timestamp, src) VALUES (?, ?, ?)";
	else
		sql = "UPDATE sync_events SET last = ?, timestamp = ? WHERE src = ?";

	rc = sqlite3_prepare(fsess->dbconn, sql, -1, &stmt, NULL);
	assert(rc == SQLITE_OK);

	sqlite3_bind_int64(stmt, 1, (sqlite_int64)fsess->client.next);
	sqlite3_bind_int64(stmt, 2, (sqlite_int64)fsess->server.next);
	sqlite3_bind_text(stmt, 3, get_client_ident(fsess), -1, NULL);

	if ((rc = sqlite3_step(stmt)) != SQLITE_DONE)
		g_critical("Error updating sync_events: %s", sqlite3_errmsg(fsess->dbconn));

	sqlite3_finalize(stmt);

	if (rc != SQLITE_DONE)
		return FALSE;

	return TRUE;
}

/*****************************************************************************/

static unsigned int add_dump_artists(FiveSession *fsess, GList **chglist)
{
	sqlite3_stmt *stmt;
	const char *sql;
	unsigned int len = 0;
	int rc;

	sql =
		"   SELECT * "
		"     FROM artists "
		"    WHERE pending_delete = 0";

	rc = sqlite3_prepare(fsess->dbconn, sql, -1, &stmt, NULL);
	assert(rc == SQLITE_OK);

	while ((rc = sqlite3_step(stmt)) == SQLITE_ROW)
	{
		FiveChange *chg = g_new0(FiveChange, 1);
		GString *data;

		chg->type = SML_CHANGE_ADD;
		chg->guid = sqlite3_column_int64(stmt, 0);
		chg->luid = -1;
		chg->mime = "application/x-fivedb-artist";

		item_set_artist_data(chg, stmt, 1);

		*chglist = g_list_append(*chglist, chg);
		len++;
	}

	if (rc != SQLITE_DONE)
		g_critical("sqlite3_step returned %d: %s\n", rc, sqlite3_errmsg(fsess->dbconn));

	sqlite3_finalize(stmt);

	return len;
}

static unsigned int add_dump_albums(FiveSession *fsess, GList **chglist)
{
	sqlite3_stmt *stmt;
	const char *sql;
	unsigned int len = 0;
	int rc;

	sql =
		"   SELECT * "
		"     FROM albums "
		"    WHERE pending_delete = 0";

	rc = sqlite3_prepare(fsess->dbconn, sql, -1, &stmt, NULL);
	assert(rc == SQLITE_OK);

	while ((rc = sqlite3_step(stmt)) == SQLITE_ROW)
	{
		FiveChange *chg = g_new0(FiveChange, 1);
		GString *data;

		chg->type = SML_CHANGE_ADD;
		chg->guid = sqlite3_column_int64(stmt, 0);
		chg->luid = -1;
		chg->mime = "application/x-fivedb-album";

		item_set_album_data(chg, -1, stmt, 1);

		*chglist = g_list_append(*chglist, chg);
		len++;
	}

	if (rc != SQLITE_DONE)
		g_critical("sqlite3_step returned %d: %s\n", rc, sqlite3_errmsg(fsess->dbconn));

	sqlite3_finalize(stmt);

	return len;
}

static unsigned int add_dump_songs(FiveSession *fsess, GList **chglist)
{
	sqlite3_stmt *stmt;
	const char *sql;
	unsigned int len = 0;
	int rc;

	sql =
		"   SELECT _id, title, artist_id, album_id, length, track, genre, "
		"          discovery_date, size "
		"     FROM songs "
		"    WHERE pending_delete = 0";

	rc = sqlite3_prepare(fsess->dbconn, sql, -1, &stmt, NULL);
	assert(rc == SQLITE_OK);

	while ((rc = sqlite3_step(stmt)) == SQLITE_ROW)
	{
		FiveChange *chg = g_new0(FiveChange, 1);
		GString *data;

		chg->type = SML_CHANGE_ADD;
		chg->guid = sqlite3_column_int64(stmt, 0);
		chg->luid = -1;
		chg->mime = "application/x-fivedb-song";

		item_set_song_data(chg, -1, -1, stmt, 0);

		*chglist = g_list_append(*chglist, chg);
		len++;
	}

	if (rc != SQLITE_DONE)
		g_critical("sqlite3_step returned %d: %s\n", rc, sqlite3_errmsg(fsess->dbconn));

	sqlite3_finalize(stmt);

	return len;
}

/*****************************************************************************/

static unsigned int add_changes_artists(FiveSession *fsess, GList **chglist,
  time_t lastAnchor)
{
	sqlite3_stmt *stmt;
	const char *sql;
	unsigned int len = 0;
	int rc;

	sql =
	  "   SELECT c.event, c.item_id, m.luid, i.* "
	  "     FROM item_changelog c "
	  "LEFT JOIN artists i ON i._id = c.item_id "
	  "LEFT JOIN item_map m ON m.guid = c.item_id AND m.domain = ? "
	  "    WHERE c.domain = ? "
	  "      AND c.timestamp > ? "
	  " ORDER BY c.timestamp ASC, c._id ASC ";

	rc = sqlite3_prepare(fsess->dbconn, sql, -1, &stmt, NULL);
	assert(rc == SQLITE_OK);

	sqlite3_bind_text(stmt, 1, "artist", -1, SQLITE_STATIC);
	sqlite3_bind_text(stmt, 2, "artist", -1, SQLITE_STATIC);
	sqlite3_bind_int64(stmt, 3, (sqlite_int64)lastAnchor);

	while ((rc = sqlite3_step(stmt)) == SQLITE_ROW)
	{
		FiveChange *chg = g_new0(FiveChange, 1);
		GString *data;

		chg->type = sqlite3_column_int(stmt, 0);
		chg->guid = sqlite3_column_int64(stmt, 1);

		if (sqlite3_column_type(stmt, 2) != SQLITE_NULL)
			chg->luid = sqlite3_column_int64(stmt, 2);
		else
			chg->luid = -1;

		chg->mime = "application/x-fivedb-artist";

		if (chg->type != SML_CHANGE_DELETE)
			item_set_artist_data(chg, stmt, 3);
		else
		{
			chg->datasz = 0;
			chg->data = NULL;
		}

		*chglist = g_list_append(*chglist, chg);
		len++;
	}

	if (rc != SQLITE_DONE)
		g_critical("sqlite3_step returned %d: %s\n", rc, sqlite3_errmsg(fsess->dbconn));

	sqlite3_finalize(stmt);

	return len;
}

static unsigned int add_changes_albums(FiveSession *fsess, GList **chglist,
  time_t lastAnchor)
{
	sqlite3_stmt *stmt;
	const char *sql;
	unsigned int len = 0;
	int rc;

	sql =
	  "   SELECT c.event, c.item_id, m.luid, "
	  "          martist.luid AS artist_luid, i.* "
	  "     FROM item_changelog c "
	  "LEFT JOIN albums i ON i._id = c.item_id "
	  "LEFT JOIN item_map m ON m.guid = c.item_id AND m.domain = ? "
	  "LEFT JOIN item_map martist ON martist.guid = i.artist_id AND martist.domain = 'artist' "
	  "    WHERE c.domain = ? "
	  "      AND c.timestamp > ? "
	  " ORDER BY c.timestamp ASC, c._id ASC ";

	rc = sqlite3_prepare(fsess->dbconn, sql, -1, &stmt, NULL);
	assert(rc == SQLITE_OK);

	sqlite3_bind_text(stmt, 1, "album", -1, SQLITE_STATIC);
	sqlite3_bind_text(stmt, 2, "album", -1, SQLITE_STATIC);
	sqlite3_bind_int64(stmt, 3, (sqlite_int64)lastAnchor);

	while ((rc = sqlite3_step(stmt)) == SQLITE_ROW)
	{
		FiveChange *chg = g_new0(FiveChange, 1);
		GString *data;

		chg->type = sqlite3_column_int(stmt, 0);
		chg->guid = sqlite3_column_int64(stmt, 1);

		if (sqlite3_column_type(stmt, 2) != SQLITE_NULL)
			chg->luid = sqlite3_column_int64(stmt, 2);
		else
			chg->luid = -1;

		chg->mime = "application/x-fivedb-album";

		if (chg->type != SML_CHANGE_DELETE)
			item_set_album_data(chg, 3, stmt, 4);
		else
		{
			chg->datasz = 0;
			chg->data = NULL;
		}

		*chglist = g_list_append(*chglist, chg);
		len++;
	}

	if (rc != SQLITE_DONE)
		g_critical("sqlite3_step returned %d: %s\n", rc, sqlite3_errmsg(fsess->dbconn));

	sqlite3_finalize(stmt);

	return len;
}

static unsigned int add_changes_songs(FiveSession *fsess, GList **chglist,
  time_t lastAnchor)
{
	sqlite3_stmt *stmt;
	const char *sql;
	unsigned int len = 0;
	int rc;

	sql =
	  "   SELECT c.event, c.item_id, m.luid, "
	  "          martist.luid AS artist_luid, malbum.luid AS album_luid, i.* "
	  "     FROM item_changelog c "
	  "LEFT JOIN songs i ON i._id = c.item_id "
	  "LEFT JOIN item_map m ON m.guid = c.item_id AND m.domain = ? "
	  "LEFT JOIN item_map martist ON martist.guid = i.artist_id AND martist.domain = 'artist' "
	  "LEFT JOIN item_map malbum ON malbum.guid = i.album_id AND malbum.domain = 'album' "
	  "    WHERE c.domain = ? "
	  "      AND c.timestamp > ? "
	  " ORDER BY c.timestamp ASC, c._id ASC ";

	rc = sqlite3_prepare(fsess->dbconn, sql, -1, &stmt, NULL);
	assert(rc == SQLITE_OK);

	sqlite3_bind_text(stmt, 1, "song", -1, SQLITE_STATIC);
	sqlite3_bind_text(stmt, 2, "song", -1, SQLITE_STATIC);
	sqlite3_bind_int64(stmt, 3, (sqlite_int64)lastAnchor);

	while ((rc = sqlite3_step(stmt)) == SQLITE_ROW)
	{
		FiveChange *chg = g_new0(FiveChange, 1);

		chg->type = sqlite3_column_int(stmt, 0);
		chg->guid = sqlite3_column_int64(stmt, 1);

		if (sqlite3_column_type(stmt, 2) != SQLITE_NULL)
			chg->luid = sqlite3_column_int64(stmt, 2);
		else
			chg->luid = -1;

		chg->mime = "application/x-fivedb-song";

		if (chg->type != SML_CHANGE_DELETE)
			item_set_song_data(chg, 3, 4, stmt, 5);
		else
		{
			chg->datasz = 0;
			chg->data = NULL;
		}

		*chglist = g_list_append(*chglist, chg);
		len++;
	}

	if (rc != SQLITE_DONE)
		g_critical("sqlite3_step returned %d: %s\n", rc, sqlite3_errmsg(fsess->dbconn));

	sqlite3_finalize(stmt);

	return len;
}

/*****************************************************************************/

GList *five_session_get_changes(FiveSession *fsess, unsigned int *len)
{
	GList *chglist = NULL;
	unsigned int lenlocal = 0;

	assert(fsess != NULL);

	if (fsess->type == SML_ALERT_REFRESH_FROM_SERVER_BY_SERVER)
	{
		lenlocal += add_dump_artists(fsess, &chglist);
		lenlocal += add_dump_albums(fsess, &chglist);
		lenlocal += add_dump_songs(fsess, &chglist);
	}
	else
	{
		lenlocal += add_changes_artists(fsess, &chglist, fsess->server.last);
		lenlocal += add_changes_albums(fsess, &chglist, fsess->server.last);
		lenlocal += add_changes_songs(fsess, &chglist, fsess->server.last);
	}

	if (len != NULL)
		*len = lenlocal;

	return chglist;
}

void five_change_free(FiveChange *chg)
{
	assert(chg != NULL);

	g_free(chg->data);
	g_free(chg);
}
